﻿namespace DotNetCommon.Extensions
{
    using DotNetCommon;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// <see cref="FileInfo"/> 和 <see cref="DirectoryInfo"/>的扩展类
    /// </summary>
    public static class FileAndDirectoryInfoExtensions
    {
        private const int DefaultBufferSize = 4096;
        private const char CarriageReturn = '\r';
        private const char NewLine = '\n';
        private const char Tab = '\t';

        /// <summary>
        /// 获取当前文件夹的大小(单位：B，包含子文件夹)
        /// </summary>
        public static long GetSizeInBytes(this DirectoryInfo directoryInfo)
        {
            var length = directoryInfo.GetFiles().AsParallel().Sum(file => file.Exists ? file.Length : 0);
            length += directoryInfo.GetDirectories().Sum(dir => dir.Exists ? dir.GetSizeInBytes() : 0);
            return length;
        }

        /// <summary>
        /// 获取当前文件夹的大小(人类易读的)
        /// </summary>
        public static string GetSizeInHumanize(this DirectoryInfo directoryInfo)
        {
            var length = GetSizeInBytes(directoryInfo);
            return UnitConverter.Humanize(length);
        }

        /// <summary>
        /// 判断当前文件夹是否是隐藏的
        /// </summary>
        public static bool IsHidden(this DirectoryInfo directoryInfo) => (directoryInfo.Attributes & FileAttributes.Hidden) != 0;

        /// <summary>
        /// 判断当前文件是否是隐藏的
        /// </summary>
        public static bool IsHidden(this FileInfo fileInfo) => (fileInfo.Attributes & FileAttributes.Hidden) != 0;

        /// <summary>
        /// 将当前文件重命名为 <paramref name="newName"/> 并返回新的文件<see cref="FileInfo"/>.
        /// </summary>
        public static FileInfo Rename(this FileInfo fileInfo, string newName)
        {
            Ensure.NotNull(fileInfo, nameof(fileInfo));
            Ensure.Exists(fileInfo);

            Ensure.NotNullOrEmptyOrWhiteSpace(newName);
            Ensure.That(newName.IsValidFileName(), $"Invalid file name: '{newName}'");

            var renamedFilePath = Path.Combine(fileInfo.DirectoryName
                                               ?? throw new InvalidOperationException(), newName);
            fileInfo.MoveTo(renamedFilePath);
            return new FileInfo(renamedFilePath);
        }

        /// <summary>
        /// 以<see cref="FileShare.ReadWrite"/>方式，打开文件流，有<seealso cref="FileAccess.Read"/>权限
        /// </summary>
        public static FileStream OpenSequentialRead(this FileInfo file) =>
            new FileStream(
                file.FullName,
                FileMode.Open,
                FileAccess.Read,
                FileShare.ReadWrite,
                DefaultBufferSize,
                FileOptions.SequentialScan);

        /// <summary>
        /// 以<see cref="FileShare.ReadWrite"/>方式，新建或打开文件流，有<seealso cref="FileAccess.Read"/>权限
        /// </summary>
        public static FileStream OpenOrCreateSequentialRead(this FileInfo file) =>
            new FileStream(
                file.FullName,
                FileMode.OpenOrCreate,
                FileAccess.Read,
                FileShare.ReadWrite,
                DefaultBufferSize,
                FileOptions.SequentialScan);

        /// <summary>
        /// 以<see cref="FileShare.Read"/>方式，打开或文件流，有<seealso cref="FileAccess.Write"/>权限
        /// </summary>
        public static FileStream OpenOrCreateSequentialWrite(this FileInfo file) =>
            new FileStream(
                file.FullName,
                FileMode.OpenOrCreate,
                FileAccess.Write,
                FileShare.Read,
                DefaultBufferSize,
                FileOptions.SequentialScan);

        /// <summary>
        /// 以<see cref="FileShare.ReadWrite"/>方式，打开或文件流，有<seealso cref="FileAccess.ReadWrite"/>权限
        /// </summary>
        public static FileStream OpenOrCreateSequentialReadWrite(this FileInfo file) =>
            new FileStream(
                file.FullName,
                FileMode.OpenOrCreate,
                FileAccess.ReadWrite,
                FileShare.ReadWrite,
                DefaultBufferSize,
                FileOptions.SequentialScan);

        /// <summary>
        /// 以UTF8编码方式读取所有文本行
        /// </summary>
        public static IEnumerable<string> ReadLines(this FileInfo file) => file.ReadLines(Encoding.UTF8);

        /// <summary>
        /// 以指定的编码方式读取所有文本行
        /// </summary>
        public static IEnumerable<string> ReadLines(this FileInfo file, Encoding encoding)
        {
            Ensure.NotNull(file, nameof(file));
            Ensure.NotNull(encoding, nameof(encoding));

            using var fs = file.OpenOrCreateSequentialRead();
            using var reader = new StreamReader(fs, encoding);

            string line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line;
            }
        }

        /// <summary>
        /// 根据指定的匹配规则<paramref name="searchPattern"/>列举出当前文件夹下的所有子文件夹
        /// </summary>
        public static IEnumerable<DirectoryInfo> EnumerateDirectoriesSafe(this DirectoryInfo directory,
            string searchPattern = "*",
            SearchOption option = SearchOption.TopDirectoryOnly,
            bool throwOnPathTooLong = false)
        {
            try
            {
                var directories = Enumerable.Empty<DirectoryInfo>();
                if (option == SearchOption.AllDirectories)
                {
                    directories = directory.EnumerateDirectories()
                        .SelectMany(d => d.EnumerateDirectoriesSafe(searchPattern, option, throwOnPathTooLong));
                }

                return directories.Concat(directory.EnumerateDirectories(searchPattern));
            }
            catch (Exception ex) when (ex is UnauthorizedAccessException || ex is PathTooLongException && !throwOnPathTooLong)
            {
                return Enumerable.Empty<DirectoryInfo>();
            }
        }

        /// <summary>
        /// 根据指定的匹配规则<paramref name="searchPattern"/>列举出当前文件夹下的所有子文件
        /// </summary>
        public static IEnumerable<FileInfo> EnumerateFilesSafe(this DirectoryInfo directory,
            string searchPattern = "*",
            SearchOption option = SearchOption.TopDirectoryOnly,
            bool throwOnPathTooLong = false)
        {
            try
            {
                var files = Enumerable.Empty<FileInfo>();
                if (option == SearchOption.AllDirectories)
                {
                    files = directory.EnumerateDirectories()
                        .SelectMany(d => d.EnumerateFilesSafe(searchPattern, option, throwOnPathTooLong));
                }

                return files.Concat(directory.EnumerateFiles(searchPattern));
            }
            catch (Exception ex) when (ex is UnauthorizedAccessException || ex is PathTooLongException && !throwOnPathTooLong)
            {
                return Enumerable.Empty<FileInfo>();
            }
        }

        /// <summary>
        /// 判断当前文件是否是二进制格式
        /// </summary>
        public static bool IsBinary(this FileInfo file)
        {
            var buffer = new char[256];

            using var fs = file.OpenOrCreateSequentialRead();
            using var reader = new StreamReader(fs);

            var read = reader.ReadBlock(buffer, 0, buffer.Length);
            return ContainsBinary(buffer, read);

            static bool ContainsBinary(char[] bytes, int count)
            {
                for (var i = 0; i < count; i++)
                {
                    var c = bytes[i];
                    if (char.IsControl(c) && c != CarriageReturn && c != NewLine && c != Tab)
                    {
                        return true;
                    }
                }
                return false;
            }
        }
    }
}